﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="KeyboardHookEx.cs" company="ScRePt">
//   Copyright © ScRePt 2010 - 2012
// </copyright>
// <summary>
//   The key event handler ex.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace MP3D.Helper
{
    #region Using Directives

    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;

    using MediaPortal.Hooks;
    using MediaPortal.Util;

    #endregion

    /// <summary>
    /// The key event handler ex.
    /// </summary>
    /// <param name="sender">
    /// The sender.
    /// </param>
    /// <param name="e">
    /// The e.
    /// </param>
    public delegate void KeyEventHandlerEx(object sender, ref KeyEventArgs e);

    /// <summary>
    /// The keyboard hook.
    /// </summary>
    public class KeyboardHookEx
    {
        #region Constants and Fields

        /// <summary>
        /// The _hook delegate.
        /// </summary>
        private readonly Win32API.HookDelegate hookDelegate;

        /// <summary>
        /// The _hook type.
        /// </summary>
        private readonly Win32API.HookType hookType;

        /// <summary>
        /// The _hook handle.
        /// </summary>
        private IntPtr hookHandle = IntPtr.Zero;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="KeyboardHookEx"/> class.
        /// </summary>
        /// <param name="hookType">
        /// The hook type.
        /// </param>
        public KeyboardHookEx(Win32API.HookType hookType)
        {
            this.hookType = hookType;
            this.hookDelegate = new Win32API.HookDelegate(this.InternalHookDelegate);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="KeyboardHookEx"/> class.
        /// </summary>
        /// <param name="hookType">
        /// The hook type.
        /// </param>
        /// <param name="hookDelegate">
        /// The hook delegate.
        /// </param>
        public KeyboardHookEx(Win32API.HookType hookType, Win32API.HookDelegate hookDelegate)
        {
            this.hookType = hookType;
            this.hookDelegate = hookDelegate;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="KeyboardHookEx"/> class. 
        /// Initializes a new instance of the <see cref="KeyboardHook"/> class.
        /// </summary>
        public KeyboardHookEx()
            : this(Win32API.HookType.WH_KEYBOARD_LL)
        {
        }

        #endregion

        #region Events

        /// <summary>
        /// The key down.
        /// </summary>
        public event KeyEventHandlerEx KeyDown;

        /// <summary>
        /// The key up.
        /// </summary>
        public event KeyEventHandlerEx KeyUp;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether IsEnabled.
        /// </summary>
        public bool IsEnabled
        {
            get
            {
                return this.hookHandle != IntPtr.Zero;
            }

            set
            {
                if (value && this.hookHandle == IntPtr.Zero)
                {
                    // this.hookHandle = Win32API.SetWindowsHookEx(this.hookType, this.hookDelegate, Marshal.GetHINSTANCE(Assembly.GetEntryAssembly().GetModules()[0]), 0);
                    this.hookHandle = Win32API.SetWindowsHookEx(this.hookType, this.hookDelegate, GetModuleHandle(), 0);
                }
                else if (value == false && this.hookHandle != IntPtr.Zero)
                {
                    Win32API.UnhookWindowsHookEx(this.hookHandle);

                    this.hookHandle = IntPtr.Zero;
                }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The get module handle.
        /// </summary>
        /// <param name="lpModuleName">
        /// The lp module name.
        /// </param>
        /// <returns>
        /// </returns>
        [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);

        #endregion

        #region Methods

        /// <summary>
        /// The get module handle.
        /// </summary>
        /// <returns>
        /// </returns>
        private static IntPtr GetModuleHandle()
        {
            IntPtr ret = Marshal.GetHINSTANCE(typeof(MediaPortal.App).Assembly.GetModules()[0]);

            if (ret != IntPtr.Zero)
            {
                return ret;
            }

            using (Process curProcess = Process.GetCurrentProcess())
            {
                using (ProcessModule curModule = curProcess.MainModule)
                {
                    return GetModuleHandle(curModule.ModuleName);
                }
            }
        }

        /// <summary>
        /// The internal hook delegate.
        /// </summary>
        /// <param name="code">
        /// The code.
        /// </param>
        /// <param name="wparam">
        /// The w param.
        /// </param>
        /// <param name="lparam">
        /// The l param.
        /// </param>
        /// <returns>
        /// The internal hook delegate.
        /// </returns>
        private int InternalHookDelegate(int code, int wparam, IntPtr lparam)
        {
            if (code == 0)
            {
                HookEventArgs e = new HookEventArgs(code, wparam, lparam);
                KeyEventArgs e2;

                if (e.WParam == 256 && this.KeyDown != null)
                {
                    KeyboardHookStruct khs = new KeyboardHookStruct(e);
                    e2 = new KeyEventArgs((Keys)khs.VirtualKey | Control.ModifierKeys);
                    this.KeyDown(this, ref e2);
                    e.Handled = e2.Handled; // The actual reason behind this Class ... e.Handled was never returned from invokers
                }
                else if (e.WParam == 257 && this.KeyUp != null)
                {
                    KeyboardHookStruct khs = new KeyboardHookStruct(e);
                    e2 = new KeyEventArgs((Keys)khs.VirtualKey | Control.ModifierKeys);
                    this.KeyUp(this, ref e2);
                    e.Handled = e2.Handled; // The actual reason behind this Class ... e.Handled was never returned from invokers
                }

                if (e.Handled)
                {
                    return 1;
                }
            }

            return Win32API.CallNextHookEx(this.hookHandle, code, wparam, lparam);
        }

        #endregion

        // ReSharper disable FieldCanBeMadeReadOnly.Local
        // ReSharper disable MemberCanBePrivate.Local

        /// <summary>
        /// The keyboard hook struct.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        private struct KeyboardHookStruct
        {
            #region Constants and Fields

            /// <summary>
            /// The virtual key.
            /// </summary>
            public int VirtualKey;

            /// <summary>
            /// The scan code.
            /// </summary>
            public int ScanCode;

            /// <summary>
            /// The flags.
            /// </summary>
            public int Flags;

            /// <summary>
            /// The time.
            /// </summary>
            public int Time;

            /// <summary>
            /// The dw extra info.
            /// </summary>
            public int DwExtraInfo;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="KeyboardHookStruct"/> struct.
            /// </summary>
            /// <param name="e">
            /// The e.
            /// </param>
            public KeyboardHookStruct(HookEventArgs e)
            {
                KeyboardHookStruct khs = (KeyboardHookStruct)Marshal.PtrToStructure(e.LParam, typeof(KeyboardHookStruct));

                this.VirtualKey = khs.VirtualKey;
                this.ScanCode = khs.ScanCode;
                this.Flags = khs.Flags;
                this.Time = khs.Time;
                this.DwExtraInfo = khs.DwExtraInfo;
            }

            #endregion
        }

        // ReSharper restore MemberCanBePrivate.Local
        // ReSharper restore FieldCanBeMadeReadOnly.Local
    }
}